
import Config.ConfigReader;
import DatabaseManager.DatabaseManager;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
import Server.Client;
import SystemDataTypes.Airport;
import SystemDataTypes.FlightData;
import SystemDataTypes.FlightPlan;
import SystemDataTypes.FlightState;
import SystemDataTypes.TowerEvent;
import SystemDataTypes.Vector3;
import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONObject;

/**
 *
 * @author Administrador
 */
public class Calculator {
    
    int subscriptionBusDataPort;
    String subscriptionBusDataHost;
    public String localAirportId;
    String salioDe; // para indicar dónde estuvo por ultima vez el vuelo
    				// calculado
    
    public Calculator()
    {
        subscriptionBusDataPort = 4001;
        subscriptionBusDataHost = "localhost";        
        try {
            // vemos en quÃ© puerto escucha el listener
            subscriptionBusDataPort = ConfigReader.getInt("subscriptionBusDataPort");
            subscriptionBusDataHost = ConfigReader.getString("subscriptionBusDataHost");
            localAirportId = ConfigReader.getString("airportId");
        } catch (Exception ex) {
           System.out.println(ex.getMessage());
        }
    }
    
    /**
     * Si se ha ingresado un nuevo dato para un vuelo, se llama a este
     * metodo de Calculator, para indicar que calcule su estado.
     * @param flightId
     */
    public void calculate(int flightId, boolean forward, boolean enviable){

    	DatabaseManager db = new DatabaseManager();

        FlightData datas[] = db.retrieveLatestFlightDatas(flightId);
        
        FlightPlan plan = db.retrieveLatestFlightPlan(flightId);

         if(plan==null && datas==null) return;

        System.out.println("Calculo estado de vuelo " + flightId);
        String estado = calculate(datas, plan);
        
        System.out.println("Estado: " + estado);
        
     if (estado==null) return;
     if(!forward) return;
     
        // Calculado el estado, lo mandamos al event bus
        JSONObject msg = new JSONObject();
        try {        	
            Client c = new Client(ConfigReader.getString("EstimationSubscriptionBusHost"), ConfigReader.getInt("EstimationSubscriptionBusDataPort"));
            FlightState fs = new FlightState();
            fs.flightId = flightId;
            fs.state = estado;
            if(enviable) fs.type="enviable"; else fs.type="noEnviable";
            msg.put("FlightState", fs.toJSONString());
            c.sendNoResponseMessage(fs.toJSONString());
            c.close();
            c = null;
        } catch (Exception ex) {
            Logger.getLogger(Calculator.class.getName()).log(Level.SEVERE, null, ex);
        }
     
    }

        // ESTAS DEBERÃ�AN SER GLOBALES
        double POSICION_DIFERENCIA_MAXIMA_TOLERANCIA = 3;
        Vector3 AIRPORT_POSITION = new Vector3(0,0,0);
        
        // Para ajustar el calculo de tiempo de arribo.
        double FactorDeAjuste=1;
          
        private class Statictics
        {            
            public Vector3 promedioPosicion=new Vector3();
            public double promedioVelocidad=0;
            public double maximaVariacionDeDistancia=0;                        
            
            @Override
            public String toString()
            {
                return ("[" + promedioPosicion.x + "," + promedioPosicion.y + "," + promedioPosicion.z + "], promedio_velocidad:" +
                        promedioVelocidad + ", maximaVariacionDeDistancia:" + maximaVariacionDeDistancia );
            }
            public Statictics(FlightData[] fd)
            {
                // Calculamos el promedio de la posiciÃ³n y la velocidad
                for (int i=0; i<fd.length; i++)    
                {
                    promedioPosicion.sumar(fd[i].position);
                    promedioVelocidad+=fd[i].speed;
                }
                promedioPosicion.dividir(fd.length);
                promedioVelocidad/=fd.length;
                
                // Calculamos la mÃ¡xima diferencia de posiciones.
                // Seguro se puede hacer de una manera mas eficiente, pero
                // estoy muuuy cansado :)
                for(int i=0; i<fd.length-1; i++)
                {
                    for(int j=i+1; j<=fd.length-1; j++)
                    {
                        double dist = fd[i].position.distancia(fd[j].position);
                        if(dist>maximaVariacionDeDistancia) maximaVariacionDeDistancia=dist;
                    }
                }
            }
        }
            
        // Calcula el tiempo de Arribo (en horas) a partir de las estadÃ­sticas.
        private double calcularTiempoDeArribo(Statictics s, FlightPlan fp, TowerEvent te)
        {        
        	System.out.println("Calculo hora de arribo");
        	
        	if(te==null)
        	{
        		double distanciaFromAirport = AIRPORT_POSITION.distancia(s.promedioPosicion);
            	double horasParaLlegar  = distanciaFromAirport / (s.promedioVelocidad*FactorDeAjuste);
            	return horasParaLlegar;
        	}
        	else
        	{
            	// vemos a qué aeropuerto debe llegar ahora
        		int salioDe = 0;
        		for(int i=0; i<fp.stops.length;i++){
        			if(fp.stops[i].equalsIgnoreCase(te.idAeropuerto)) salioDe=i;
        		}
        		
        		// esto significaría que el avión salió del último aeropuerto
        		// y no puede ser
        		if(salioDe==fp.stops.length-1) return -1;
        		
            	DatabaseManager db = new DatabaseManager();
            	Airport proximo = db.retrieveAirport(fp.stops[salioDe+1]);
            	System.out.println("Proxima parada: " + proximo.id);

        		// aquí salioDe es el indice en stops de donde salió el aeropuerto
        		// debemos calcular cuánto falta para el siguiente.    
            	
            	System.out.println("posicion vuelo: " + s.promedioPosicion.toString());
            	System.out.println("posicion proximo: " + proximo.position.toString());
            	
        		double distanciaFromAirport = proximo.position.distancia(s.promedioPosicion);
            	double horasParaLlegar  = distanciaFromAirport / (s.promedioVelocidad*FactorDeAjuste);

            	int myIndex=-1;
            	// vemos cual es nuestro indice en el arreglo de paradas
            	for(int i=0; i<fp.stops.length; i++)
            	{
            		if(fp.stops[i].equalsIgnoreCase(localAirportId)) myIndex = i;            	
            	}
            	System.out.println("myIndex:" + myIndex);
            	
                if(myIndex==-1)
            	{
            		System.out.println("NO estoy en el plan de vuelo! ERROR!");
            		return -1;
            	}
            		
            	// Ahora vemos cuánto falta para llegar a mí (asumiendo que no sea yo el proximo)
            	for(int i=salioDe+1; i<myIndex-1;i++)
            	{            	
            		System.out.println("Calculo entre " + i  + " y " + i+1);
            		distanciaFromAirport = db.retrieveAirport(fp.stops[i]).position.distancia(db.retrieveAirport(fp.stops[i+1]).position);
                	horasParaLlegar += distanciaFromAirport / (s.promedioVelocidad*FactorDeAjuste);            		
            	}
            	return horasParaLlegar; 
        	}
        }
                
        private String calculate(FlightData[] flightDataArray, FlightPlan flightPlan)
        {                       
            
            if(flightPlan == null) return null;

            if(flightPlan.cancelled) return "Cancelado";
            
            // Pedimos el último TowerEvent de este vuelo
            DatabaseManager db = new DatabaseManager();
            TowerEvent te;
            te = db.retrieveLatestTowerEvent(flightPlan.flightId);            

            // si no tenemos informaciÃ³n sobre el vuelo, puede
            // estar programado o desconocido.
            if(flightDataArray==null || flightDataArray.length ==0)
            {
                Calendar cHoraActual = Calendar.getInstance();
                Calendar cHoraPartidaProgramada = Calendar.getInstance();
                cHoraPartidaProgramada.setTime(flightPlan.leavingDate);
                System.out.println(flightPlan.leavingDate.getHours());

                if(cHoraActual.before(cHoraPartidaProgramada)){
                return "Programado";
                }else{
                	if(te==null) return "Desconocido";
                }
            }

            Statictics s = new Statictics(flightDataArray);
                       
            if(te!=null)
            {
            	if(te.tipo.equalsIgnoreCase("aterrizaje"))
            	{
            		if(te.demora==0)
            			return "Aterrizado en " + te.idAeropuerto;
            		else
            			return "Aterrizará en " + te.idAeropuerto + " en " + te.demora;            			
            	}
            	else
            	{
            		if(te.demora>0)
            		{
            			return "Despegará de " + te.idAeropuerto + " en " + te.demora + " minutos" ;            			
            		}
            		else
            		{            		
	            		// qué pasa si despegó? 
	            		// debemos ver si la última información que tenemos es que 
	            		// despegó, o si está volando. Comparamos los timestamps
	            		// del TowerEvent y el último FlightData
	            	
	            		FlightData fd = db.retrieveLatestFlightData(flightPlan.flightId);
	            		if(fd!=null)
	            		{
	                        Calendar cFd = Calendar.getInstance();                       
	                        Calendar cTe = Calendar.getInstance();
	                        cFd.setTime(db.retrieveLatestFlightDataTimeStamp(flightPlan.flightId));
	                        cTe.setTime(db.retrieveLatestTowerEventTimeStamp(flightPlan.flightId));
	                        // ahora vemos cual es anterior
	                        if(cTe.before(cFd))
	                		{
	                        	// si el TowerEvent fue anterior, entonces
	                        	// está volando, y sabemos a qué velocidad y
	                        	// en dónde está : Calculamos cuánto falta para llegar
	                        	// a destino.
	                        	salioDe = te.idAeropuerto;
	//                        	return "está volando porque acaba de salir de un aeropuerto, calcular!";
	                		}
	                        else
	                        {
	                        	// si el TowerEvent fue después, entonces no
	                        	// tenemos datos nuevos del avión, sólo sabemos
	                        	// que despegó.     
	                        	return "Despegó de " + te.idAeropuerto;
	                        }
	            		}else
	            		{
	            			// si no hay flight datas, entonces el avión despegó
	            			// y es lo único que sabemos.
	                    	return "Despegó de " + te.idAeropuerto;
	            		}
            		}
            		
            	}
            }            
            
            // Si alguna entrada difiere demasiado.
            if(s.maximaVariacionDeDistancia>POSICION_DIFERENCIA_MAXIMA_TOLERANCIA)
            {
                return "Error_Tolerancia";
            }
            
            // Si está aquí podemos calcular cuánto falta para que llegue a 
            // destino. salioDe dice de dónde salió el avion. 
            
            // Calculamos los minutos que falta para que llegue.
            int minutosParaArribar = (int)(calcularTiempoDeArribo(s, flightPlan, te)*60);
            //System.out.println("minutos para arribar: " + minutosParaArribar);
            // Obtenemos la hora actual.
            Calendar cHoraActual = Calendar.getInstance();
            // Obtenemos la hora de llegada estimada.
            Calendar cHoraArriboEstimada = Calendar.getInstance();
            cHoraArriboEstimada.add(Calendar.MINUTE, minutosParaArribar);

            // Obtenemos un calendario de la fecha de arribo y salida programada.
            Calendar cHoraArriboProgramada = Calendar.getInstance();
           
            cHoraArriboProgramada.setTime(flightPlan.arrivingDate);
            Calendar cHoraPartidaProgramada = Calendar.getInstance();
            cHoraPartidaProgramada.setTime(flightPlan.leavingDate);
            
            // Si la hora actual es menor a la planificada de salida...
            if (cHoraActual.getTime().before(cHoraPartidaProgramada.getTime()))
            {
                return "Programado";
            }
            // Si la hora de arribo calculada es igual a la hora actual...
            if(cHoraArriboEstimada.getTime().equals(cHoraActual.getTime()))
            {
                return "Aterrizado";
            }

            System.out.print("Actual: "); System.out.println(timeToString(cHoraActual));
            System.out.print("Estimada para llegar: "); System.out.println(timeToString(cHoraArriboEstimada));
            System.out.print("Hora arribo programada: "); System.out.println(timeToString(cHoraArriboProgramada));
            System.out.println("Minutos para arribar: " + minutosParaArribar);

            if(cHoraArriboEstimada.after(cHoraArriboProgramada))
            {
                long hpMillis = cHoraArriboProgramada.getTimeInMillis();
                long heMillis = cHoraArriboEstimada.getTimeInMillis();
                long diffMinutes = (heMillis-hpMillis) / (60 * 1000);
                return "Demorado - Atrasado " + diffMinutes + " minutos (estimada:"+ timeToString(cHoraArriboEstimada)+")";
            }

            if(cHoraArriboEstimada.before(cHoraArriboProgramada) || cHoraArriboEstimada.getTime().equals(cHoraArriboProgramada.getTime()))    
            {
                return "A tiempo - Arribo en " + minutosParaArribar + " minutos (estimada:"+ timeToString(cHoraArriboEstimada)+")";
            }
            
            
            return "Desconocido";
        }    

       void calculateAll() {           
           DatabaseManager db = new DatabaseManager();
           int ids[] = db.retrieveFlightsId();
           
           if(ids==null) return;
           for(int i=0;i<ids.length;i++) 
           {
               calculate(ids[i], true, false);
           }
        }

    private String timeToString(Calendar cHoraActual) {
        String s = cHoraActual.get(Calendar.DAY_OF_MONTH) + "/" +
                   cHoraActual.get(Calendar.MONTH) + "/" +
                   cHoraActual.get(Calendar.YEAR) + "@"+
                   cHoraActual.get(Calendar.HOUR_OF_DAY) + ":"+
                   cHoraActual.get(Calendar.MINUTE);
        return s;
    }
}













